/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package hhf.flex.ascreator;

import com.sun.mirror.declaration.ClassDeclaration;
import com.sun.mirror.declaration.MethodDeclaration;
import com.sun.mirror.declaration.ParameterDeclaration;
import com.sun.mirror.declaration.TypeDeclaration;
import com.sun.mirror.type.ClassType;
import com.sun.mirror.type.InterfaceType;
import com.sun.mirror.type.ReferenceType;
import hhf.flex.annotations.entities.FlexTransient;
import hhf.flex.ascreator.creators.ASGenericServiceCreator;
import hhf.flex.ascreator.exceptions.IgnoredClassException;
import hhf.flex.ascreator.exceptions.IllegalClassException;
import hhf.flex.ascreator.exceptions.NoAnnotationClassException;
import hhf.flex.ascreator.exceptions.NoCommentClassException;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author François
 */
public class ASCompatibilityServiceCreator extends ASCreator {

	//	hérité
	//	protected String path;
	//	protected Class _class;

	private final String id;
	private final Class _class;
	private final TypeDeclaration typeDeclaration;

	/**
	 * Créé un service flex.
	 * @param path : racine du package
	 * @param clazz : class à créer
	 * @param id : destination flex
	 * @param options : options : legacy-collection
	 */
	public ASCompatibilityServiceCreator(final String path, final Class clazz, final String id, final Map<String, Object> options) {
		super(path, options);
		this._class = clazz;
		this.id = id;
		this.typeDeclaration = (TypeDeclaration) options.get("typeDeclaration");
	}

	@Override
	protected String getASPackage() {
		return _class.getPackage().getName();
	}

	@Override
	protected String getASClassName() {
		return _class.getSimpleName();
	}

	@Override
	protected boolean isInterface() {
		return false;
	}

	@Override
	protected String getASSuperClassName() {
		return "flex.services.AbstractService";
	}

	@Override
	protected Collection<String> getASInterfacesName() {
		return null;
	}

	@Override
	protected Set<String> getASImports() {
		Set<String> imports = getASDefaultImports();
		getASImportsFromClass(_class, imports);
		for (Method method : _class.getMethods()) {
			if(method.isAnnotationPresent(FlexTransient.class)) continue;
			getASImportsFromMethod(method, imports);
		}
		return imports;
	}

	/**
	 * Retourne une liste d'imports par defaut.
	 * @return Set<String>
	 */
	private Set<String> getASDefaultImports() {
		Set<String> imports = new HashSet<String>();
		imports.add("flex.services.events.ServiceEvent");
		imports.add("flex.services.AbstractService");
		imports.add("flash.utils.ByteArray");
//		imports.add("mx.core.Application");
		imports.add("mx.rpc.Responder");
		imports.add("mx.rpc.events.ResultEvent");
		imports.add("mx.rpc.events.FaultEvent");
		imports.add("mx.core.mx_internal");
		imports.add("mx.collections.ArrayCollection");
		imports.add("mx.utils.ObjectUtil");
		imports.add("mx.rpc.AbstractOperation");
		imports.add("mx.rpc.AsyncToken");
		imports.add("mx.rpc.remoting.Operation");
		imports.add("mx.rpc.remoting.RemoteObject");
		imports.add("mx.utils.UIDUtil");
		return imports;
	}

	@Override
	protected Map<String, String> getASEvents() {
		return new HashMap<String, String>();
	}

	@Override
	protected Set<String> getASNamespaces() {
		Set<String> nss = new HashSet<String>();
		nss.add("mx_internal");
		return nss;
	}

	@Override
	protected void writeASBodyConstructor(final OutputStreamWriter out) throws IOException {
		out.write("\t\t\tsuper(\"" + id + "\");\n");
	}

	@Override
	protected String getASClassComment() throws NoCommentClassException {
		// on regarde si la declaration de la classe comporte un commentaire.
		String comment = typeDeclaration.getDocComment();
		if (comment == null) { // sinon on regarde sur les interfaces
			Collection<InterfaceType> interfaces = typeDeclaration.getSuperinterfaces();
			for (InterfaceType inter : interfaces) {
				comment = inter.getDeclaration().getDocComment();
				if (comment != null) {
					return comment;
				}
			}
		} else {
			return comment;
		}
		throw new NoCommentClassException();
	}

	/**
	 * 
	 * @param out
	 * @throws IOException
	 */
	@Override
	protected void writeASFieldsAndMethods(final OutputStreamWriter out) throws IOException {
		for (Method method : _class.getMethods()) {
			if(method.isAnnotationPresent(FlexTransient.class)) continue;
			if(options.get("ignore-deprecated")!=null) {
				if(method.isAnnotationPresent(Deprecated.class) && options.get("ignore-deprecated").equals("true")) continue;
			}
			try {
				Object.class.getDeclaredMethod(method.getName(), method.getParameterTypes());
			} catch (NoSuchMethodException ex) {
				// Creation de la methode pour ORM coté Flex
				ASMethodClassCreator asMethodClassCreator = new ASMethodClassCreator(path, method, options);
				asMethodClassCreator.write();

				Collection<ParameterDeclaration> paramDeclarations = null;
				String comment = null;
				// Retrouve la methode dans la déclaration retournée par APT
				// TODO à améliorer au niveau de la signature de la methode, et pour le commentaire, regarder les interfaces
				MethodDeclaration methodDeclaration = null;
				for (MethodDeclaration m : typeDeclaration.getMethods()) {
					if (m.getSimpleName().equals(method.getName())) {
						comment = m.getDocComment();
						methodDeclaration = m;
						paramDeclarations = m.getParameters();
						break;
					}
				}
				if(methodDeclaration!=null) {
					reportExceptionsFromMethodDeclaration(methodDeclaration);
				} else {
					reportExceptionsFromMethod(method);
				}
//				for(Class exceptionClass : method.getExceptionTypes()) {
//					ASClassCreator asClassCreator = new ASClassCreator(path, exceptionClass, options);
//					try {
//						asClassCreator.write();
//					} catch (IllegalClassException ex1) {
//						Logger.getLogger(ASServiceCreator.class.getName()).log(Level.SEVERE, "Cannot Create exceptionclass {0} from method : {1}.{2} : {3}", new Object[]{exceptionClass.getSimpleName(), _class.getSimpleName(), method.getName(), ex.getMessage()});
//					}
//				}
				// Recupere les types concernés
				String returnType = ASCreatorTools.getASType(method.getReturnType(), legacyCollection);
				List<String> args = ASCreatorTools.getASArguments(method, legacyCollection); // ordonnés
				List<String> argNames = new ArrayList<String>();

				// Récupere le nom des arguments
				if (paramDeclarations != null) {
					for (ParameterDeclaration paramDeclaration : paramDeclarations) {
						argNames.add(paramDeclaration.getSimpleName());
					}
				} else {
					for (int i = 0; i < args.size(); i++) {
						argNames.add("arg" + i);
					}

				}
				// Ecrit le commentaire de la methode
				writeMethodComment(out, method, comment, args, argNames, returnType);
				// Ecrit la methode
				out.write("\t\tpublic function " + method.getName() + "(");
				int i = 0;
				if(argNames.size()!=args.size()) {
					Logger.getLogger(ASGenericServiceCreator.class.getName()).log(Level.SEVERE, "Cannot Create service {0} cause method {1} arguments inconsistent - argNames : {2} / args : {3}", new Object[]{_class.getSimpleName(), method.getName(), argNames.size(), args.size()});
				}
				while (i < argNames.size()) {
					out.write(argNames.get(i) + ":" + args.get(i));
					if ((++i) < argNames.size()) {
						out.write(", ");
					}
				}
				out.write("):AsyncToken {\n");
				// Ecrit le corp de la methode
				writeASMethodBody(out, method, argNames, returnType);
				out.write("\t\t\treturn token;\n");
				out.write("\t\t}\n\n");
			}
		}
	}

	/**
	 * Reporte les exceptions dans le fichier adéquat
	 * @param method
	 */
	private void reportExceptionsFromMethod(final Method method) {
		Properties errorsProperties = (Properties) options.get("errorsProperties");
		Class[] exceptions = method.getExceptionTypes();
		String serviceName = _class.getName() + "." + method.getName();
		if (!errorsProperties.containsKey(serviceName)) { // messagedisant que le service à échoué.
			errorsProperties.put(serviceName, serviceName.toUpperCase());
		}
		for (Class except : exceptions) {
			String fullEntry = serviceName + "." + except.getName();
			if (!errorsProperties.containsKey(fullEntry)) { // message disant que le service à échoué pour l'exception suivante
				errorsProperties.put(fullEntry, fullEntry.toUpperCase());
			}
			if (!errorsProperties.containsKey(except.getName())) { // message disant qu'une erreur c'est produite.
				errorsProperties.put(except.getName(), except.getName().toUpperCase());
			}
		}
	}

	/**
	 * Reporte les exceptions dans le fichier adéquat
	 * @param method
	 */
	private void reportExceptionsFromMethodDeclaration(final MethodDeclaration method) {
		Properties errorsProperties = (Properties) options.get("errorsProperties");
		Collection<ReferenceType> exceptions = method.getThrownTypes();
		for (ReferenceType except : exceptions) {
		String serviceName = _class.getName() + "." + method.getSimpleName();
			if (!errorsProperties.containsKey(serviceName)) { // messagedisant que le service à échoué.
				errorsProperties.put(serviceName, serviceName.toUpperCase());
			}
			String fullEntry = serviceName + "." + except.toString();
			if (!errorsProperties.containsKey(fullEntry)) { // message disant que le service à échoué pour l'exception suivante
				errorsProperties.put(fullEntry, fullEntry.toUpperCase());
			}
			String msg = except.toString().toUpperCase();
			if(except instanceof ClassType) {
				ClassDeclaration exceptDeclaration = ((ClassType) except).getDeclaration();
				String comment = exceptDeclaration.getDocComment();
				if(comment!=null) {
					StringTokenizer tokenizer = new StringTokenizer(comment, "\n");
					if(tokenizer.hasMoreTokens()) {
						msg = tokenizer.nextToken().trim();
					}
				}
			}
			if (!errorsProperties.containsKey(except.toString())) { // message disant qu'une erreur c'est produite.
				errorsProperties.put(except.toString(), msg);
			}
		}
	}
	/**
	 * Ecrit le commentaire de la methode
	 * 
	 */
	private void writeMethodComment(final OutputStreamWriter out, final Method method, final String methodComment, final List<String> args, final List<String> argNames, final String returnType)
			  throws IOException {
		out.write("\t\t/**\n");
		// un commentaire existe déjà
		if (methodComment != null) {
			String comment = methodComment.replaceAll("\n", "\n\t\t *");
			out.write("\t\t *" + comment + "/\n");
		} else { // on en construit un
			for (int i = 0; i < args.size(); i++) {
				String arg = argNames.get(i) + " : " + args.get(i);
				out.write("\t\t * @param " + arg + "\n");
			}
			if (!"void".equals(returnType)) {
				out.write("\t\t * @return result:" + returnType + "\n");
			}
			Class[] exceptions = method.getExceptionTypes();
			for (Class except : exceptions) {
				out.write("\t\t * @throws " + except.getName() + "\n");
			}
			out.write("\t\t */\n");
		}
	}

	/**
	 * Ecrit le corps de la methode
	 * @param out
	 * @param methods
	 */
	private void writeASMethodBody(final OutputStreamWriter out, final Method method, final List<String> argNames, String returnASType) throws IOException {
		out.write("\t\t\tvar operationName:String = \"" + method.getName() + "\";\n");
		out.write("\t\t\tvar uid:String = UIDUtil.createUID();\n");
		out.write("\t\t\tvar operation:AbstractOperation = getOperation(operationName, uid);\n");
		if (!"void".equals(returnASType)) {
			out.write("\t\t\toperation.resultType = " + returnASType + ";\n");
		}
		out.write("\t\t\tvar token:AsyncToken = operation.send(");
		int i = 0;
		while (i < argNames.size()) {
			out.write(argNames.get(i));
			if ((++i) < argNames.size()) {
				out.write(", ");
			}
		}
		out.write(");\n");
		out.write("\t\t\ttoken.addResponder(getResponder(operationName, uid));\n");
	}

	@Override
	protected void checkClass() throws IllegalClassException, IgnoredClassException {
		return;
	}

	@Override
	protected void createSeeAlsoASClass() {
	}

	@Override
	protected Collection<String> getASClassAnnotations() throws NoAnnotationClassException {
		return null;
	}
	
}
